/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 ****************************************************************/
package org.epiphron.convert;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;

/**
 * Retrieves annotation information as well as other type related operations on
 * Classes. Keeps cached versions to minimize introspection work.
 * 
 */
abstract class TypeWrapper {
	protected String NS;
	protected Class<?> c;
	protected BeanInfo info;
	protected Constructor<?> constructor;
	protected PropertyDescriptor[] descriptors;
	private static HashMap<Class<?>, TypeWrapper> cache = new HashMap<Class<?>, TypeWrapper>();

	protected <T> TypeWrapper(Class<T> c, String uriPrefix) {
		this.c = c;
		info = beanInfo(c);
		Namespace nsa = c.getAnnotation(Namespace.class);
		NS = (nsa != null) ? nsa.value() : uriPrefix;
		try {
			constructor = c.getConstructor(String.class);
		} catch (Exception e) {}
		cache.put(c, this);
	}
	
	public static synchronized TypeWrapper type(Object o, String uriPrefix) {
		return wrap(o.getClass(), uriPrefix);
	}

	public static ValuesContext[] valueContexts(Object o, String uriPrefix) {
		return type(o, uriPrefix).getValueContexts(o);
	}
	
	public ValuesContext[] getValueContexts(Object o) {
		ArrayList<FieldContext> values = new ArrayList<FieldContext>();
		for (Field field : ConversionUtil.getDeclaredFields(c)) {
			if (field.isAnnotationPresent(Transient.class)) {
				 continue;
			}
			if (!Modifier.isTransient(field.getModifiers())) {
				values.add(new FieldContext(o, NS, field));
			}
		}
		return values.toArray(new ValuesContext[0]);
	}

	public static synchronized TypeWrapper wrap(Class<?> c, String uriPrefix) {
		return (cache.containsKey(c)) ? cache.get(c) : TypeWrapperFactory.newWrapper(c, uriPrefix);
	}

	public String typeUri() {
		return NS + ConversionUtil.getRdfType(c);
	}

	public String namespace() {
		return NS;
	}

	public abstract String uri(String id);

	public String uri(Object bean) {
		return uri(id(bean));
	}

	public String uri(AccessibleObject m, String name) {
		RdfProperty rdf = getRDFAnnotation(m);
		
		if (rdf != null) {
			String val = rdf.value();
			
			// applying namespace, if needed
			if (!val.contains("#")) {
				return namingPatternUri(val);
			}
			return val;
		}
		return namingPatternUri(name);
	}

	protected static RdfProperty getRDFAnnotation(AccessibleObject m) {
		return m.getAnnotation(RdfProperty.class);
	}

	private String namingPatternUri(String name) {
		return namespace() + name;
	}

	public static String instanceURI(Object bean, String uriPrefix) {
		return type(bean, uriPrefix).uri(bean);
	}

	/**
	 * Reterns the ID
	 * 
	 * @param bean
	 * @return
	 */
	public abstract String id(Object bean);

	protected static BeanInfo beanInfo(Class<?> c) {
		try {
			return Introspector.getBeanInfo(c);
		} catch (IntrospectionException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	protected String invokeMethod(Object bean, Method me) {
		try {
			Object o = me.invoke(bean);
			return (o==null) ? null: o.toString();
		} catch (Exception e) {
			ConversionUtil.LOGGER.log(Level.WARNING, "Failed invoking method " + 
					me.getName() + " on class " + bean.getClass() , e);
		}
		return null;
	}

}
